/*
 * OffsetLinksAction.java
 * 
 * Created by demory on Jan 13, 2009, 11:17:31 PM
 * 
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.fpdev.apps.rtemaster.actions;

import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.fpdev.core.basenet.BLink;
import org.fpdev.core.basenet.BNode;
import org.fpdev.core.basenet.BaseNetGraph;
import org.fpdev.core.basenet.Path;
import org.fpdev.apps.rtemaster.RouteMaster;
import org.fpdev.util.FPUtil;

/**
 *
 * @author demory
 */
public class OffsetLinksAction implements ACAction {

  private BaseNetGraph graph_;
  private BNode startNode_;
  private double dist_;

  private BNode[] nodes_;

  public OffsetLinksAction(BaseNetGraph graph, BNode startNode, double dist) {
    graph_ = graph;
    startNode_ = startNode;
    dist_ = dist;
  }

  public boolean doAction(RouteMaster ac) {
    Path path = new Path(ac.getEngine());
    graph_.convertSimpleGraphToPath(path, startNode_);

    List<BNode> origNodes = path.getNodeList();
    nodes_ = new BNode[path.linkCount() + 1];

    Point2D.Double sPt = this.offsetNodeFromLink(path.startLink(), path.startNode(), dist_);
    nodes_[0] = ac.getNetworkOps().newNode(sPt.x, sPt.y).getNode();
    //av_.getGUI().getMapPanel().getDrawItems().addItem("ps", new HighlightedNode(new CNode(-1, null, sPt.x, sPt.y), Color.blue, 8), "m");


    Point2D.Double ePt = this.offsetNodeFromLink(path.endLink(), path.endNode(), -dist_);
    System.out.println("ePt=" + ePt.toString());
    nodes_[nodes_.length - 1] = ac.getNetworkOps().newNode(ePt.x, ePt.y).getNode();
    //av_.getGUI().getMapPanel().getDrawItems().addItem("pl", new HighlightedNode(new CNode(-1, null, ePt.x, ePt.y), Color.red, 8), "m");

    for (int n = 1; n < path.linkCount(); n++) {
      Point2D.Double mPt = this.offsetNodeFromLinks(path.getLink(n - 1), path.getLink(n), dist_);
      System.out.println("n+" + n + ": " + mPt.x + "," + mPt.y);

      //CNode newMNode = new CNode(-1, null, mPt.x, mPt.y);
      //av_.getGUI().getMapPanel().getDrawItems().addItem("p"+n, new HighlightedNode(newMNode, Color.green, 8), "m");

      //create node
      nodes_[n] = ac.getNetworkOps().newNode(mPt.x, mPt.y).getNode();
    }

    for (int l = 0; l < path.linkCount(); l++) {
      BLink link = path.getLink(l);

      List<Point2D.Double> newShpPts = null;

      // process shapePoints, if necessary
      if (link.shapePointsCount() > 0) {
        boolean inLine = origNodes.get(l) == link.getFNode();
        Line2D.Double[] lines = new Line2D.Double[link.shapePointsCount() + 1];

        Iterator<Point2D.Double> shpPts = link.getShapePoints(); //shpPts_.iterator();
        double x1 = link.getX1(), y1 = link.getY1(), x2, y2;

        int i = 0;//(nodes[l] == link.getFNode()) ? 0 : lines.length-1;
        while (shpPts.hasNext()) {
          Point2D.Double shpPt = shpPts.next();
          x2 = shpPt.getX();
          y2 = shpPt.getY();
          //canvas.drawLine(x1, y1, x2, y2);
          if (inLine) {
            lines[i] = new Line2D.Double(x1, y1, x2, y2);
          } else {
            lines[lines.length - 1 - i] = new Line2D.Double(x2, y2, x1, y1);
          }
          x1 = x2;
          y1 = y2;
          i++;// += (nodes[l] == link.getFNode() ? 1 : -1);
        }
        x2 = link.getX2();
        y2 = link.getY2();
        //canvas.drawLine(x1, y1, x2, y2);
        if (inLine) {
          lines[i] = new Line2D.Double(x1, y1, x2, y2);
        } else {
          lines[lines.length - 1 - i] = new Line2D.Double(x2, y2, x1, y1);
        }

        // create new shapepoints
        newShpPts = new ArrayList<Point2D.Double>();
        for (int s = 0; s < link.shapePointsCount(); s++) {
          Point2D.Double oldShpPt = link.getShapePointsList().get(inLine ? s : link.shapePointsCount() - 1 - s);
          if (!oldShpPt.equals(link.getFNode().toPoint()) && !oldShpPt.equals(link.getTNode().toPoint())) {
            newShpPts.add(offsetPointFromLines(lines[s], lines[s + 1], dist_));
          }
        }
      }

      //create link
      ac.getNetworkOps().newLink(null, nodes_[l], nodes_[l + 1], 0, newShpPts, 0, true);
    }

    return true;
  }


  private Point2D.Double offsetNodeFromLink(BLink link, BNode node, double dist) {
    Point2D.Double pt1 = node.toPoint(), pt2 = link.getNearestShapePoint(node);

    Line2D.Double vect = FPUtil.createNormalizedVector(pt1, pt2, Math.abs(dist));
    AffineTransform at = AffineTransform.getRotateInstance((dist > 0 ? -Math.PI / 2 : Math.PI / 2), pt1.x, pt1.y);
    Point2D.Double pt2prime = new Point2D.Double();
    at.transform(vect.getP2(), pt2prime);
    return pt2prime;
  }

  private Point2D.Double offsetNodeFromLinks(BLink link1, BLink link2, double dist) {
    Point2D pt1 = null, pt2 = null, pt3 = null;

    if (link1.getFNode() == link2.getFNode()) {
      pt1 = link1.getNearestShapePoint(link1.getFNode());
      pt2 = link1.getFNode().toPoint();
      pt3 = link2.getNearestShapePoint(link1.getFNode());
    } else if (link1.getTNode() == link2.getTNode()) {
      pt1 = link1.getNearestShapePoint(link1.getTNode());
      pt2 = link1.getTNode().toPoint();
      pt3 = link2.getNearestShapePoint(link1.getTNode());
    } else if (link1.getFNode() == link2.getTNode()) {
      pt1 = link1.getNearestShapePoint(link1.getFNode());
      pt2 = link1.getFNode().toPoint();
      pt3 = link2.getNearestShapePoint(link1.getFNode());
    } else if (link1.getTNode() == link2.getFNode()) {
      pt1 = link1.getNearestShapePoint(link1.getTNode());
      pt2 = link1.getTNode().toPoint();
      pt3 = link2.getNearestShapePoint(link1.getTNode());
    } else {
      System.out.println("Error in offsetNodeFromLinks(): links are not adjacent");
      return null;
    }
    return this.offsetPointFromLines(new Line2D.Double(pt1, pt2), new Line2D.Double(pt2, pt3), dist);
  }

  private Point2D.Double offsetPointFromLines(Line2D.Double seg1, Line2D.Double seg2, double dist) {

    Point2D ptA = seg1.getP1(), ptB = seg2.getP2(), ptC = seg1.getP2();

    // use law of cosines to find angle between lines
    double a = FPUtil.magnitude(ptB.getX(), ptB.getY(), ptC.getX(), ptC.getY());
    double b = FPUtil.magnitude(ptA.getX(), ptA.getY(), ptC.getX(), ptC.getY());
    double c = FPUtil.magnitude(ptA.getX(), ptA.getY(), ptB.getX(), ptB.getY());

    double aCosVal = (a * a + b * b - c * c) / (2 * a * b);
    double C = (aCosVal > -1 && aCosVal < 1) ? Math.acos(aCosVal) : Math.PI;
    Line2D.Double vect = FPUtil.createNormalizedVector(ptC, ptB, Math.abs(dist));

    double rot; // angle of rotation
    if (Line2D.relativeCCW(ptA.getX(), ptA.getY(), ptC.getX(), ptC.getY(), ptB.getX(), ptB.getY()) == -1) {
      rot = (dist > 0) ? rot = -(2 * Math.PI - C) / 2 : C / 2;
    } else {
      rot = (dist > 0) ? rot = -C / 2 : (2 * Math.PI - C) / 2;
    }

    AffineTransform at = AffineTransform.getRotateInstance(rot, ptC.getX(), ptC.getY());
    Point2D.Double pt2prime = new Point2D.Double();
    at.transform(vect.getP2(), pt2prime);
    return pt2prime;
  }

  public boolean undoAction(RouteMaster ac) {
    return new DeleteNodesAction(new HashSet<BNode>(Arrays.asList(nodes_))).doAction(ac);
  }

  public String getName() {
    return "Offset Links";
  }

}
